package org.longteng.sensitive.impl;

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import org.longteng.sensitive.annotation.EncryptField;
import org.longteng.sensitive.ISensitiveDataEncrypt;
import org.longteng.core.utils.Reflections;
import org.longteng.sensitive.utils.SensitiveUtil;
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertyResolver;
import com.ulisesbocchio.jasyptspringboot.properties.JasyptEncryptorConfigurationProperties;
import lombok.extern.slf4j.Slf4j;
import org.jasypt.encryption.StringEncryptor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;

/**
 * 敏感数据加解密Jasypt实现
 * <br>@Bean public ISensitiveDataEncrypt(
 *             @Qualifier(EncryptablePropertyResolverConfiguration.RESOLVER_BEAN_NAME) final EncryptablePropertyResolver encPropertyResolver,
 *             @Qualifier(ENCRYPTOR_BEAN_NAME) final StringEncryptor encryptor,
 *             final ConfigurableEnvironment environment) {
 *                 return new SensitiveDataJasyptImpl(encPropertyResolver,environment);
 *             }
 */
@Slf4j
public class SensitiveDataJasyptImpl implements ISensitiveDataEncrypt {

    /**spring配置变量*/
    private final Environment environment;
    /**加解密类*/
    private final StringEncryptor encryptor;
    /**是否加密生成ENC()前缀*/
    private final boolean markENCryptPrefix;
    /**前缀配置*/
    private final JasyptEncryptorConfigurationProperties props;
    private EncryptablePropertyResolver encPropertyResolver;

    public void setEncPropertyResolver(EncryptablePropertyResolver encPropertyResolver) {
        this.encPropertyResolver = encPropertyResolver;
    }

    private AES aesCbc;

    /**
     * 构造
     * <br>在app在建Bean注入生成
     * @param encPropertyResolver 注入已有@Qualifier(EncryptablePropertyResolverConfiguration.RESOLVER_BEAN_NAME)
     * @param encryptor 注入已有@Qualifier(ENCRYPTOR_BEAN_NAME)
     * @param environment  注入spring 有配置变量
     * @param markENCryptPrefix  加密是否加ENC()
     */
    public SensitiveDataJasyptImpl(
           final EncryptablePropertyResolver encPropertyResolver,final StringEncryptor encryptor,
           final ConfigurableEnvironment environment, boolean markENCryptPrefix) {
        this.setEncPropertyResolver(encPropertyResolver);
        this.environment = environment;
        this.encryptor = encryptor;
        this.markENCryptPrefix = markENCryptPrefix;
        this.props = JasyptEncryptorConfigurationProperties.bindConfigProps(environment);

        // aesCBC构建 ，this.props.getPassword()
        String keyPws = HexUtil.encodeHexStr(SensitiveUtil.KEY_STR);
        byte[] keys = SensitiveUtil.getBytes(keyPws, 32);
        byte[] iv = SensitiveUtil.getBytes(SensitiveUtil.IV_KEY,16);

        this.aesCbc = new AES(Mode.CBC, Padding.PKCS5Padding, keys, iv);
        if(log.isDebugEnabled()) {
            log.debug("创建字段加解密aesCbc handler成功，CBC。");
        }
    }

    /**
     * 加密, 如：查询参数加密
     *
     * @param param 可能是实体或string字段
     * @return 返回加密串，按注解指定mode处理
     */
    @Override
    public Object encrypt(Object param) {
        return this.encrypt(param, null);
    }

    /**
     * 加密, 如：查询参数加密
     * @param param 可能是实体或string字段
     * @param etyField 指定实体的field，以取注解
     * @return 返回加密串，按注解指定mode处理
     */
    @Override
    public Object encrypt(Object param, Field etyField) {
        Class<?> paramClass = param.getClass();
        if (String.class == paramClass) {
            boolean aesMode = false;
            if(etyField != null) {
                EncryptField encryptField = etyField.getAnnotation(EncryptField.class);
                if (Objects.nonNull(encryptField)) {
                    aesMode = "AESCBD".equals(encryptField.mode());
                }
            }
            return aesMode? this.aesEncrypt((String) param) : this.jasyptEncrypt((String) param);
        } else {
            Field[] fields = paramClass.getDeclaredFields();

            try {
                BeanInfo beanInfo = Introspector.getBeanInfo(paramClass);

                for (Field field : fields) {
                    EncryptField encryptField = field.getAnnotation(EncryptField.class);
                    if (Objects.nonNull(encryptField)) {
                        // field.setAccessible(true); // 安全扫描不通过
                        PropertyDescriptor declaredField = Reflections.getDeclaredFieldProper(beanInfo, field.getName());
                        if (Objects.nonNull(declaredField)) {
                            if (declaredField.getPropertyType() == String.class) {
                                Method getter = declaredField.getReadMethod();
                                Object value = getter.invoke(param);
                                if (value instanceof String) {
                                    Method setter = declaredField.getWriteMethod();
                                    // 按配置模式选加密算法
                                    String encValue = "AESCBD".equals(encryptField.mode()) ? this.aesEncrypt((String) value)
                                            : this.jasyptEncrypt((String) value);
                                    setter.invoke(param, encValue);
                                }
                            }
                        } else {
                            /* Object fieldInstance = field.get(param);
                            if (fieldInstance instanceof String) {
                                field.set(param, this.jasyptEncrypt((String) fieldInstance));
                            } */
                            log.warn("encrypt()加密失效，未找到declaredField，通过field反射不安全.");
                        }
                    }
                }
            } catch (IllegalAccessException | InvocationTargetException | IntrospectionException e) {
                log.error("jaspty加密处理取属性错误.", e);
            }
        }
        return param;
    }

    /**
     * 解密，如：数据查询结果解密
     *
     * @param result
     * @return 可能是实体或string字段
     */
    @Override
    public Object decrypt(Object result) {
        Class<?> resultClass = result.getClass();
        Field[] fields = resultClass.getDeclaredFields();
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(resultClass);
            for (Field field : fields) {
                EncryptField annotation = field.getAnnotation(EncryptField.class);
                if (Objects.nonNull(annotation)) {
                    // field.setAccessible(true);  // 安全扫描不通过
                    PropertyDescriptor declaredField = Reflections.getDeclaredFieldProper(beanInfo, field.getName());
                    if (Objects.nonNull(declaredField)) {
                        if (declaredField.getPropertyType() == String.class) {
                            Method getter = declaredField.getReadMethod();
                            Object value = getter.invoke(result);
                            if (value instanceof String) {
                                Method setter = declaredField.getWriteMethod();
                                // 按配置模式选加密算法
                                String decValue = "AESCBD".equals(annotation.mode()) ? this.aesDecrypt((String) value)
                                        : this.jasyptDecrypt((String) value);
                                setter.invoke(result, decValue);
                            }
                        }
                    } else {
                        /*Object fieldInstance = field.get(result);
                        if (fieldInstance instanceof String) {
                            field.set(result, this.jasyptDecrypt((String) fieldInstance));
                        }*/
                        log.warn("decrypt()解密失效，未找到declaredField，通过field反射不安全.");
                    }
                }
            }
        } catch (IllegalAccessException | InvocationTargetException | IntrospectionException e) {
            log.error("jaspty解密处理错误.", e);
        }
        return result;
    }

    // private static JasyptStatelessService jasyptStatelessService = new JasyptStatelessService();

    /*
     * 加密
     * @param input
     * @param password
     * @param algorithm
     * @return
    public static String encrypt(String input, String password,String algorithm){
        /*return jasyptStatelessService.encrypt(input, password,null,null,algorithm,
                null,null,null,null,
                null,null,null,
                null,null,null,
                null,null,null,
                null,null,null,
                null,
                null, null,null);

    }
     */

    /**
     * jaspty加密
     * @param value
     * @return
     */
    private String jasyptEncrypt(String value) {
        if(value == null) {
            return value;
        }
        if(this.encryptor == null) {
            log.warn("未注入Jasypt的属性StringEncryptor实现类，没法实现解密。");
            throw new RuntimeException("not found Jasypt StringEncryptor  Bean.");
        } else {
            if (log.isDebugEnabled()) {
                log.debug("注入Jasypt的属性StringEncryptor实现类:{}", this.encryptor.getClass());
            }
        }
        String encValue = this.encryptor.encrypt(value);
        if(this.markENCryptPrefix) {
            return String.format("%s%s%s", props.getProperty().getPrefix(), encValue, props.getProperty().getSuffix());
        } else {
            return encValue;
        }
    }

    /**
     * jaspty解密
     * @param value
     * @return
     */
    private String jasyptDecrypt(String value) {
        if(value == null) {
            return value;
        }
        if(this.encPropertyResolver == null) {
            log.warn("未注入Jasypt的属性EncryptablePropertyResolver实现类，没法实现解密。");
            throw new RuntimeException("not found Jasypt EncryptablePropertyResolver impl Bean.");
        } else {
            if (log.isDebugEnabled()) {
                log.debug("注入Jasypt的属性EncryptablePropertyResolver实现类:{}", this.encPropertyResolver.getClass());
            }
        }
        return this.encPropertyResolver.resolvePropertyValue(value);
    }


    /**
     * AESCBC加密
     * @param value
     * @return
     */
    public String aesEncrypt(String value) {
        if (null == value) {
            return null;
        }
        try {
            String result = aesCbc.encryptHex(value);
            return result;
        } catch (CryptoException e) {
            if(log.isDebugEnabled()) {
                log.debug("字段拦截(AESCBC)加密的值：{}", value);
            }
            log.error("字段拦截加密错误.", e);
        }
        return value;

    }

    /**
     * AESCBC解密
     * @param value
     * @return
     */
    public String aesDecrypt(String value) {
        if (null == value) {
            return null;
        }
        try {
            String result = aesCbc.decryptStr(value);
            return result;
        } catch (CryptoException e) {
            if(log.isDebugEnabled()) {
                log.debug("字段拦截(AESCBC)解密的值：{}", value);
            }
            log.error("字段拦截解密错误.", e);
        }
        return value;
    }

}
